package org.apache.commons.graph

import org.apache.commons.graph.builder.DefaultLinkedConnectionBuilder
import org.apache.commons.graph.builder.GraphConnection
import org.apache.commons.graph.builder.GraphConnector
import org.apache.commons.graph.builder.LinkedConnectionBuilder
import org.apache.commons.graph.builder.useConnector
import org.apache.commons.graph.algorithms.coloring.ColorsBuilder
import org.apache.commons.graph.algorithms.connectivity.DefaultConnectivityBuilder
import org.apache.commons.graph.algorithms.elo.DefaultRankingSelector
import org.apache.commons.graph.algorithms.elo.GameResult
import org.apache.commons.graph.algorithms.elo.RankingSelector
import org.apache.commons.graph.export.DefaultExportSelector
import org.apache.commons.graph.export.NamedExportSelector
import org.apache.commons.graph.algorithms.flow.DefaultFlowWeightedEdgesBuilder
import org.apache.commons.graph.algorithms.flow.FlowWeightedEdgesBuilder
import org.apache.commons.graph.model.DirectedMutableGraph
import org.apache.commons.graph.model.UndirectedMutableGraph
import org.apache.commons.graph.algorithms.scc.DefaultSccAlgorithmSelector
import org.apache.commons.graph.algorithms.scc.SccAlgorithmSelector
import org.apache.commons.graph.algorithms.shortestpath.DefaultWeightedEdgesSelector
import org.apache.commons.graph.algorithms.shortestpath.PathWeightedEdgesBuilder
import org.apache.commons.graph.algorithms.spanning.DefaultSpanningWeightedEdgeMapperBuilder
import org.apache.commons.graph.algorithms.spanning.SpanningWeightedEdgeMapperBuilder
import org.apache.commons.graph.algorithms.visit.DefaultVisitSourceSelector
import org.apache.commons.graph.algorithms.visit.VisitSourceSelector
import org.apache.commons.graph.bean.DefaultEdge

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

/**
 * The Apache Commons Graph package is a toolkit for managing graphs and graph based data structures.
 */
object CommonsGraph {
    /**
     * Create a color builder.
     *
     * @param <V> the Graph vertices type
     * @param <E> the Graph edges type
     * @param <G> the Graph type
     * @param graph the input graph
     * @return an instance of [ColorsBuilder]
    </G></E></V> */
    fun <V, E, G : UndirectedGraph<V, E>> coloring(graph: G): ColorsBuilder<V, E> {
        //var graph = graph
        //graph = Assertions.checkNotNull(graph, "Coloring can not be calculated on null graph")
        return org.apache.commons.graph.algorithms.coloring.DefaultColorsBuilder(graph)
    }

    /**
     * Ranks the players (vertices) that took part in a tournament (graph) depending on the game results (edges),
     * applying the [Elo Rating System](https://en.wikipedia.org/wiki/Elo_rating_system.).
     *
     * @param <P> the players involved in the tournament
     * @param <TG> the Tournament Graph type
     * @param tournamentGraph the graph representing the tournament
     * @return the builder for the functor which returns/update the players ranking
    </TG></P> */
    fun <P, TG : DirectedGraph<P, GameResult>> eloRate(tournamentGraph: TG): RankingSelector<P> {
        //var tournamentGraph = tournamentGraph
        //tournamentGraph = Assertions.checkNotNull(tournamentGraph, "ELO ranking can not be applied on null graph!")
        return DefaultRankingSelector(tournamentGraph)
    }

    /**
     * Export the graph in DOT or GraphML format.
     *
     * @param <V> the Graph vertices type
     * @param <E> the Graph edges type
     * @param <G> the Graph type
     * @param graph the input graph
     * @return an instance of [NamedExportSelector]
    </G></E></V> */
    fun <V, E, G : Graph<V, E>> export(graph: G): NamedExportSelector<V, E> {
        //var graph = graph
        //graph = Assertions.checkNotNull(graph, "Null graph can not be exported")
        return DefaultExportSelector(graph)
    }

    /**
     * Calculates the input graph Connected Component.
     *
     * @param <V> the Graph vertices type.
     * @param <E> the Graph edges type.
     * @param <G> the directed graph type
     * @param graph the Graph which connected component has to be verified.
     * @return the Connectivity algorithm builder
    </G></E></V> */
    fun <V, E, G : Graph<V, E>> findConnectedComponent(graph: G): org.apache.commons.graph.algorithms.connectivity.ConnectivityBuilder<V, E> {
        //var graph = graph
        //graph = Assertions.checkNotNull(graph, "Connected Component cannot be calculated from a null graph")
        return DefaultConnectivityBuilder(graph)
    }

    /**
     * Find the maximum flow on the input [Graph].
     *
     * @param <V> the Graph vertices type
     * @param <WE> the Graph edges type
     * @param <G> the Graph type
     * @param graph the input edge-weighted graph
     * @return an instance of [FlowWeightedEdgesBuilder]
    </G></WE></V> */
    fun <V, WE, G : DirectedGraph<V, WE>> findMaxFlow(graph: G): FlowWeightedEdgesBuilder<V, WE> {
        //var graph = graph
        //graph = Assertions.checkNotNull(graph, "Max flow can not be calculated on null graph")
        return DefaultFlowWeightedEdgesBuilder(graph)
    }

    /**
     * Find the sortest on the input [Graph]
     *
     * @param <V> the Graph vertices type
     * @param <WE> the Graph edges type
     * @param <G> the Graph type
     * @param graph the input edge-weighted graph
     * @return the caluculated the sortest
    </G></WE></V> */
    fun <V, WE, G : Graph<V, WE>> findShortestPath(graph: G): PathWeightedEdgesBuilder<V, WE> {
        //var graph = graph
        //graph = Assertions.checkNotNull(graph, "Shortest path can not be calculated on null graph")
        return DefaultWeightedEdgesSelector(graph)
    }

    /**
     * Calculates the input graph Strongly Connected Component.
     *
     * @param <V> the Graph vertices type.
     * @param <E> the Graph edges type.
     * @param <G> the directed graph type
     * @param graph the Graph which strongly connected component has to be verified.
     * @return the SCC algoritm selector
    </G></E></V> */
    fun <V, E, G : DirectedGraph<V, E>> findStronglyConnectedComponent(graph: G): SccAlgorithmSelector<V, E> {
        //var graph = graph
        //graph = Assertions.checkNotNull(graph, "Strongly Connected Component cannot be calculated from a null graph")
        return DefaultSccAlgorithmSelector(graph)
    }

    /**
     * Find the minimum spanning tree on the input [Graph]
     *
     * @param <V> the Graph vertices type
     * @param <WE> the Graph edges type
     * @param <G> the Graph type
     * @param graph the input edge-weighted graph
     * @return the caluculated minimun spanning tree
    </G></WE></V> */
    fun <V, WE, G : Graph<V, WE>> minimumSpanningTree(graph: G): SpanningWeightedEdgeMapperBuilder<V, WE> {
        //var graph = graph
        //graph = Assertions.checkNotNull(graph, "Minimum spanning tree can not be calculated on null graph")
        return DefaultSpanningWeightedEdgeMapperBuilder(graph)
    }

    /**
     * Creates a new [DirectedMutableGraph] instance where vertices
     * are connected as described in the input [GraphConnection] instance.
     *
     * @param <V> the Graph vertices type
     * @param <E> the Graph edges type
     * @param graphConnection the [GraphConnection] instance that describes vertices
     * @return a new [DirectedMutableGraph] instance
    </E></V> */
    fun <V, E> newDirectedMutableGraph(graphConnection: GraphConnection<V, E>): DirectedMutableGraph<V, E> {
        return populate(DirectedMutableGraph<V, E>()).withConnections(graphConnection)
    }

    /**
     * Creates a new [UndirectedMutableGraph] instance where vertices
     * are connected as described in the input [GraphConnection] instance.
     *
     * @param <V> the Graph vertices type
     * @param <E> the Graph edges type
     * @param graphConnection the [GraphConnection] instance that describes vertices
     * @return a new [UndirectedMutableGraph] instance
    </E></V> */
    fun <V, E> newUndirectedMutableGraph(graphConnection: GraphConnection<V, E>): UndirectedMutableGraph<V, E> {
        return populate(UndirectedMutableGraph<V, E>()).withConnections(graphConnection)
    }

    /**
     * Allows populate the given [MutableGraph].
     *
     * @param <V> the Graph vertices type
     * @param <E> the Graph edges type
     * @param <G> the Graph type
     * @param graph the graph has to be populated
     * @return the builder to configure vertices connection
    </G></E></V> */
    fun <V, E, G : MutableGraph<V, E>> populate(graph: G): LinkedConnectionBuilder<V, E, G> {
        return DefaultLinkedConnectionBuilder(graph)
    }

    /**
     * Returns a synchronized (thread-safe) [DirectedGraph] backed by the specified Graph.
     *
     * It is imperative that the user manually synchronize on the returned graph when iterating over iterable collections:
     * <pre>
     * Graph syncGraph = synchronize( graph );
     * ...
     * synchronized(syncGraph) {
     * for ( Vertex v : g.getVertices() ) // Must be in synchronized block
     * {
     * foo( v )
     * }
     * }
    </pre> *
     *
     * Failure to follow this advice may result in non-deterministic behavior.
     *
     * The returned [Graph] will be serializable if the specified [Graph] is serializable.
     *
     * @param <V> the Graph vertices type
     * @param <E> the Graph edges type
     * @param graph the input [Graph]
     * @return the syncronyzed graph
    </E></V> */
    fun <V, E> synchronize(graph: DirectedGraph<V, E>): Graph<V, E> {
        return SynchronizedDirectedGraph(graph)
    }

    /**
     * Returns a synchronized (thread-safe) [Graph] backed by the specified Graph.
     *
     * It is imperative that the user manually synchronize on the returned graph when iterating over iterable collections:
     * <pre>
     * Graph syncGraph = synchronize( graph );
     * ...
     * synchronized(syncGraph) {
     * for ( Vertex v : g.getVertices() ) // Must be in synchronized block
     * {
     * foo( v )
     * }
     * }
    </pre> *
     *
     * Failure to follow this advice may result in non-deterministic behavior.
     *
     * The returned [Graph] will be serializable if the specified [Graph] is serializable.
     *
     * @param <V> the Graph vertices type
     * @param <E> the Graph edges type
     * @param graph the input [Graph]
     * @return the syncronyzed graph
    </E></V> */
    fun <V, E> synchronize(graph: Graph<V, E>): Graph<V, E> {
        return SynchronizedGraph(graph)
    }

    /**
     * Returns a synchronized (thread-safe) [MutableGraph] backed by the specified Graph.
     *
     * It is imperative that the user manually synchronize on the returned graph when iterating over iterable collections:
     * <pre>
     * Graph syncGraph = synchronize( graph );
     * ...
     * synchronized(syncGraph) {
     * for ( Vertex v : g.getVertices() ) // Must be in synchronized block
     * {
     * foo( v )
     * }
     * }
    </pre> *
     *
     * Failure to follow this advice may result in non-deterministic behavior.
     *
     * The returned [Graph] will be serializable if the specified [Graph] is serializable.
     *
     * @param <V> the Graph vertices type
     * @param <E> the Graph edges type
     * @param graph the input [Graph]
     * @return the synchronized graph
    </E></V> */
    fun <V, E> synchronize(graph: MutableGraph<V, E>): Graph<V, E> {
        return SynchronizedMutableGraph(graph)
    }

    /**
     * Returns a synchronized (thread-safe) [UndirectedGraph] backed by the specified Graph.
     *
     * It is imperative that the user manually synchronize on the returned graph when iterating over iterable collections:
     * <pre>
     * Graph syncGraph = synchronize( graph );
     * ...
     * synchronized(syncGraph) {
     * for ( Vertex v : g.getVertices() ) // Must be in synchronized block
     * {
     * foo( v )
     * }
     * }
    </pre> *
     *
     * Failure to follow this advice may result in non-deterministic behavior.
     *
     * The returned [Graph] will be serializable if the specified [Graph] is serializable.
     *
     * @param <V> the Graph vertices type
     * @param <E> the Graph edges type
     * @param graph the input [Graph]
     * @return the syncronyzed graph
    </E></V> */
    fun <V, E> synchronize(graph: UndirectedGraph<V, E>): Graph<V, E> {
        return SynchronizedUndirectedGraph(graph)
    }

    /**
     * Allows select a series of algorithms to apply on input graph.
     *
     * @param <V> the Graph vertices type
     * @param <E> the Graph edges type
     * @param <G> the Graph type
     * @param graph the Graph instance to apply graph algorithms
     * @return the graph algorithms selector
    </G></E></V> */
    fun <V, E, G : Graph<V, E>> visit(graph: G): VisitSourceSelector<V, E, G> {
        //var graph = graph
        //graph = Assertions.checkNotNull(graph, "No algorithm can be applied on null graph!")
        return DefaultVisitSourceSelector(graph)
    }
}

fun <V, E> Graph.Companion.newDirectedMutableGraph() = DirectedMutableGraph<V, E>()

fun <V> Graph.Companion.sampleDirectedMutableGraph() = DirectedMutableGraph<V, DefaultEdge<V>>()

fun <V, E> Graph.Companion.newDirectedMutableGraph(graphConnection: GraphConnection<V, E>): DirectedMutableGraph<V, E>
         = CommonsGraph.newDirectedMutableGraph(graphConnection)

fun <V, E> Graph.Companion.newDirectedMutableGraph(block : GraphConnector<V, E>.()-> Unit): DirectedMutableGraph<V, E>
        = DirectedMutableGraph<V, E>().useConnector(block)


fun <V> Graph.Companion.sampleDirectedMutableGraph(graphConnection: GraphConnection<V, DefaultEdge<V>>): DirectedMutableGraph<V, DefaultEdge<V>>
        = CommonsGraph.newDirectedMutableGraph(graphConnection)

fun <V> Graph.Companion.sampleDirectedMutableGraph(block : GraphConnector<V, DefaultEdge<V>>.()-> Unit): DirectedMutableGraph<V, DefaultEdge<V>>
        = DirectedMutableGraph<V, DefaultEdge<V>>().useConnector(block)

fun <V, E> Graph.Companion.newUndirectedMutableGraph() = UndirectedMutableGraph<V, E>()

fun <V> Graph.Companion.sampleUndirectedMutableGraph() = UndirectedMutableGraph<V, DefaultEdge<V>>()


fun <V, E> Graph.Companion.newUndirectedMutableGraph(graphConnection: GraphConnection<V, E>): UndirectedMutableGraph<V, E>
        = CommonsGraph.newUndirectedMutableGraph(graphConnection)

fun <V, E> Graph.Companion.newUndirectedMutableGraph(block : GraphConnector<V, E>.()-> Unit): UndirectedMutableGraph<V, E>
        = UndirectedMutableGraph<V, E>().useConnector(block)

fun <V> Graph.Companion.sampleUndirectedMutableGraph(graphConnection: GraphConnection<V,  DefaultEdge<V>>): UndirectedMutableGraph<V,  DefaultEdge<V>>
        = CommonsGraph.newUndirectedMutableGraph(graphConnection)

fun <V> Graph.Companion.sampleUndirectedMutableGraph(block : GraphConnector<V,  DefaultEdge<V>>.()-> Unit): UndirectedMutableGraph<V,  DefaultEdge<V>>
        = UndirectedMutableGraph<V,  DefaultEdge<V>>().useConnector(block)